using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace GameStateManagement
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public delegate void ActorDelegate();
    public class Actor : DrawableGameComponent
    {
        public event ActorDelegate onGameOver;
        public Model ActorModel;
        public Effect effect;

        private float _m_fScale;
        public float m_fScale
        {
            set
            {
                _m_fScale = value;
                RecalcWorldTransform();
            }
            get { return _m_fScale; }
        }

        private Vector3 _m_vWorldPosition;
        public Vector3 m_vWorldPosition
        {
            set
            {
                _m_vWorldPosition = value;
                RecalcWorldTransform();
            }
            get { return _m_vWorldPosition; }
        }
        private Quaternion _m_qRotation;
        public Quaternion m_qRotation
        {
            set
            {
                _m_qRotation = value;
                RecalcWorldTransform();
            }
            get { return _m_qRotation; }
        }

        public Matrix WorldTransform;

        public Vector3 m_vVelocity = Vector3.Zero;

        public float fMass = 1.0f;
        public float fTerminalVelocity = 1000.0f;
        public Vector3 vForce = Vector3.Zero; // Look at Actor.CreateForce()
        public Vector3 vAcceleration = Vector3.Zero;
        public bool bPhysicsDriven = false;

        protected List<Force> forces = new List<Force>();

        protected string sMeshName;
        Utils.Timer timer;
        public Matrix [] ActorBones;

        public BoundingSphere ModelBounds;
        public BoundingSphere WorldBounds;

        public bool use_effect;

        public Matrix view, world, projection;
        ContentManager content;

        public Vector3 m_AmbientLightColor = Color.White.ToVector3();
        public Vector3 m_SpecularColor = Vector3.One;
        public Vector3 m_DiffuseColor = Color.White.ToVector3();

        public bool withinWormholeRadius = false;

        public Actor(Game game)
            : base(game)
        {
            timer = new Utils.Timer();

            sMeshName = "";

            _m_fScale = 1.0f;
            _m_vWorldPosition = Vector3.Zero;
            _m_qRotation = Quaternion.Identity;
            WorldTransform = Matrix.Identity;
            use_effect = false;
            // TODO: Construct any child components here
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here
            base.Initialize();
        }

        public void load_matrices(Matrix view_temp, Matrix world_temp, Matrix projection_temp, ContentManager content_temp)
        {
            view = view_temp;
            world = world_temp;
            projection = projection_temp;
            content = content_temp;
            effect = content.Load<Effect>("my_effect");
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (!GameplayScreen.pause && !GameplayScreen.gameOver)
            {
                timer.Update(gameTime);
                if (bPhysicsDriven)
                {
                    // Calculate additive force.
                    vForce = Vector3.Zero;
                    foreach (Force force in forces)
                    {
                        vForce += force.vec;
                    }

                    DrivePhysics();
                }
                base.Update(gameTime);
            }
        }

        protected override void LoadContent()
        {
            if (!String.IsNullOrEmpty(sMeshName))
            {
                ActorModel = Game.Content.Load<Model>(sMeshName);
                ActorBones = new Matrix[ActorModel.Bones.Count];

                foreach (ModelMesh mesh in ActorModel.Meshes)
                {
                    ModelBounds = BoundingSphere.CreateMerged(ModelBounds,
                    mesh.BoundingSphere);
                }
            }
            else
            {
                ActorModel = null;
            }
            RecalcWorldTransform();
            base.LoadContent();
        }

        protected override void UnloadContent()
        {
            
            base.UnloadContent();
        }

        public override void Draw(GameTime gameTime)
        {
            if (!GameplayScreen.pause && !GameplayScreen.gameOver)
            {
                if (use_effect == false)
                {
                    if (ActorModel != null)
                    {
                        // NOTE!!! The following will not compile until we define a
                        // CameraMatrix and ProjectionMatrix, which we do in part 2!!
                        GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                        //GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
                        //GraphicsDevice.BlendState = BlendState.Opaque;

                        ActorModel.CopyAbsoluteBoneTransformsTo(ActorBones);

                        foreach (ModelMesh mesh in ActorModel.Meshes)
                        {
                            foreach (BasicEffect effect in mesh.Effects)
                            {
                                effect.World = ActorBones[mesh.ParentBone.Index] * WorldTransform;
                                effect.View = GameplayScreen.FollowCamera.m_CameraMatrix;
                                effect.Projection = GameplayScreen.FollowCamera.m_ProjectionMatrix;
                                effect.EnableDefaultLighting();
                                effect.PreferPerPixelLighting = true;
                                effect.AmbientLightColor = m_AmbientLightColor;
                                effect.SpecularColor = m_SpecularColor;
                                effect.EmissiveColor = Color.Black.ToVector3();
                                //effect.SpecularPower = GameplayScreen.SpecularPower;
                                //effect.DirectionalLight0.Direction = GameplayScreen.m_vDirLight;
                                effect.DirectionalLight0.DiffuseColor = m_DiffuseColor;
                                //effect.DirectionalLight1.Direction = -GameplayScreen.m_vDirLight;
                                //effect.DirectionalLight1.DiffuseColor = Color.Red.ToVector3();
                            }

                            /*foreach (ModelMeshPart part in mesh.MeshParts)
                            {
                                part.Effect = effect;
                                effect.Parameters["World"].SetValue(GameplayScreen.CameraMatrix * mesh.ParentBone.Transform);
                                effect.Parameters["View"].SetValue(Matrix.CreateTranslation(WorldTransform.Translation));
                                effect.Parameters["Projection"].SetValue(GameplayScreen.ProjectionMatrix);
                                effect.Parameters["AmbientColor"].SetValue(Color.Green.ToVector4());
                            }*/
                            mesh.Draw();
                        }
                    }
                }
                else
                {
                    draw_with_effect();
                }
                base.Draw(gameTime);
            }
        }

        virtual public void draw_with_effect()
        {
            foreach (ModelMesh mesh in ActorModel.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = effect;
                    effect.Parameters["World"].SetValue(GameplayScreen.FollowCamera.m_CameraMatrix * mesh.ParentBone.Transform);
                    effect.Parameters["View"].SetValue(Matrix.CreateTranslation(WorldTransform.Translation));
                    effect.Parameters["Projection"].SetValue(GameplayScreen.FollowCamera.m_ProjectionMatrix);
                    effect.Parameters["AmbientColor"].SetValue(Color.Green.ToVector4());
                }
                mesh.Draw();
            }

        }

        /// <summary>
        /// Modify the returned Force object type to apply a force to the actor.  DeleteForce to remove the force.
        /// </summary>
        /// <returns></returns>
        public Force CreateForce(string name)
        {
            Force newForce = new Force(name);
            forces.Add(newForce);
            return newForce;
        }

        /// <summary>
        /// Remove this force from the actor.
        /// </summary>
        /// <param name="force"></param>
        public void DeleteForce(Force force)
        {
            force.vec = Vector3.Zero;
            forces.Remove(force);
        }

        protected virtual void DrivePhysics()
        {
            m_vVelocity += vAcceleration * GameplayScreen.fDelta / 2.0f;
            m_vWorldPosition += m_vVelocity * GameplayScreen.fDelta;
            vAcceleration = vForce / fMass;
            m_vVelocity += vAcceleration * GameplayScreen.fDelta / 2.0f;

            // Terminal velocity check
            if (m_vVelocity.LengthSquared() > Math.Pow(fTerminalVelocity, 2.0f))
            {
                m_vVelocity.Normalize();
                m_vVelocity *= fTerminalVelocity;
            }
        }

        public Vector3 GetWorldFacing()
        {
            return WorldTransform.Forward;
        }
        public Vector3 GetWorldPosition()
        {
            return WorldTransform.Translation;
        }

        public void RecalcWorldTransform()
        {
            WorldTransform = Matrix.CreateScale(m_fScale) * Matrix.CreateFromQuaternion(m_qRotation) * Matrix.CreateTranslation(m_vWorldPosition);

            WorldBounds.Center = m_vWorldPosition;
            WorldBounds.Radius = ModelBounds.Radius * m_fScale;
        }
    }
}
